package com.haiya.common.domain.model;

import com.haiya.common.AssertionConcern;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author qiaoguoqiang
 */
public abstract class EventSourcedRootEntity extends AssertionConcern {
    /**
     * 变异方法名
     */
    private static final String MUTATOR_METHOD_NAME = "when";

    /**
     * 变异方法hashmap
     */
    private static Map<String, Method> mutatorMethods =
            new HashMap<String, Method>();

    /**
     * 突变事件列表
     */
    private List<DomainEvent> mutatingEvents;

    /**
     * 未经修改的版本号
     */
    private int unmutatedVersion;

    public int mutatedVersion() {
        return this.unmutatedVersion() + 1;
    }

    public List<DomainEvent> mutatingEvents() {
        return this.mutatingEvents;
    }

    public int unmutatedVersion() {
        return this.unmutatedVersion;
    }

    /**
     * 构造函数
     *
     * @param anEventStream  事件流
     * @param aStreamVersion 事件流版本
     */
    protected EventSourcedRootEntity(
            List<DomainEvent> anEventStream,
            int aStreamVersion) {

        this();

        for (DomainEvent event : anEventStream) {
            this.mutateWhen(event);
        }

        this.setUnmutatedVersion(aStreamVersion);
    }

    /**
     * 空参构造器
     * 初始化一个容量为2的突变事件列表属性
     */
    protected EventSourcedRootEntity() {
        super();

        this.setMutatingEvents(new ArrayList<DomainEvent>(2));
    }

    /**
     * 应用领域事件: 添加一个领域事件到突变事件列表
     * @param aDomainEvent
     */
    protected void apply(DomainEvent aDomainEvent) {

        this.mutatingEvents().add(aDomainEvent);

        this.mutateWhen(aDomainEvent);
    }

    /**
     * 事件发生时执行属性setter
     * 通过反射调用私有或公共的when(eventType)方法
     * 最终设置相关属性到实例对象中。
     * @param aDomainEvent
     */
    protected void mutateWhen(DomainEvent aDomainEvent) {
        Class<? extends EventSourcedRootEntity> rootType = this.getClass();
        Class<? extends DomainEvent> eventType = aDomainEvent.getClass();
        String key = rootType.getName() + ":" + eventType.getName();
        Method mutatorMethod = mutatorMethods.get(key);
        if (mutatorMethod == null) {
            mutatorMethod = this.cacheMutatorMethodFor(key, rootType, eventType);
        }
        try {
            mutatorMethod.invoke(this, aDomainEvent);
        } catch (InvocationTargetException e) {
            if (e.getCause() != null) {
                throw new RuntimeException(
                        "Method "
                                + MUTATOR_METHOD_NAME
                                + "("
                                + eventType.getSimpleName()
                                + ") failed. See cause: "
                                + e.getMessage(),
                        e.getCause());
            }

            throw new RuntimeException(
                    "Method "
                            + MUTATOR_METHOD_NAME
                            + "("
                            + eventType.getSimpleName()
                            + ") failed. See cause: "
                            + e.getMessage(),
                    e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(
                    "Method "
                            + MUTATOR_METHOD_NAME
                            + "("
                            + eventType.getSimpleName()
                            + ") failed because of illegal access. See cause: "
                            + e.getMessage(),
                    e);
        }
    }

    /**
     * 缓存突变方法到突变方法映射，便于多次调用同一突变方法后续更高效
     * key -> method
     * key: 为参数aKey
     * method: 为具体根实体对应给定anEventType的when()方法
     * @param aKey              根实体类型名 + 事件类型名
     * @param aRootType         根实体类
     * @param anEventType       事件类型
     * @return
     */
    private Method cacheMutatorMethodFor(
            String aKey,
            Class<? extends EventSourcedRootEntity> aRootType,
            Class<? extends DomainEvent> anEventType) {
        synchronized (mutatorMethods) {
            try {
                Method method = this.hiddenOrPublicMethod(aRootType, anEventType);
                method.setAccessible(true);
                mutatorMethods.put(aKey, method);
                return method;
            } catch (Exception e) {
                throw new IllegalArgumentException(
                        "I do not understand "
                                + MUTATOR_METHOD_NAME
                                + "("
                                + anEventType.getSimpleName()
                                + ") because: "
                                + e.getClass().getSimpleName() + ">>>" + e.getMessage(),
                        e);
            }
        }
    }

    /**
     * 给定根类型的私有或公共when()方法，其参数为anEventType类型
     * @param aRootType
     * @param anEventType
     * @return
     * @throws Exception
     */
    private Method hiddenOrPublicMethod(
            Class<? extends EventSourcedRootEntity> aRootType,
            Class<? extends DomainEvent> anEventType)
            throws Exception {
        Method method = null;
        try {
            // assume protected or private...
            method = aRootType.getDeclaredMethod(
                    MUTATOR_METHOD_NAME,
                    anEventType);
        } catch (Exception e) {
            // then public...
            method = aRootType.getMethod(
                    MUTATOR_METHOD_NAME,
                    anEventType);
        }
        return method;
    }

    private void setMutatingEvents(List<DomainEvent> aMutatingEventsList) {
        this.mutatingEvents = aMutatingEventsList;
    }

    private void setUnmutatedVersion(int aStreamVersion) {
        this.unmutatedVersion = aStreamVersion;
    }
}
